;

(function($) {
    $.fn.highchartTable = function() {

        var allowedGraphTypes = ["column", "line", "area", "spline", "pie"];

        var getCallable = function(table, attribute) {
            var callback = $(table).data(attribute);
            if (typeof callback != "undefined") {
                var infosCallback = callback.split(".");
                var callable = window[infosCallback[0]];
                for (var i = 1, infosCallbackLength = infosCallback.length; i < infosCallbackLength; i++) {
                    callable = callable[infosCallback[i]];
                }
                return callable;
            }
        };

        this.each(function() {
            var table = $(this);
            var $table = $(table);
            var nbYaxis = 1;

            // Retrieve graph title from the table caption
            var captions = $("caption", table);
            var graphTitle = captions.length ? $(captions[0]).text() : "";

            var graphContainer;
            if ($table.data("graph-container-before") != 1) {
                // Retrieve where the graph must be displayed from the graph-container attribute
                var graphContainerSelector = $table.data("graph-container");
                if (!graphContainerSelector) {
                    throw "graph-container data attribute is mandatory";
                }

                if (graphContainerSelector[0] === "#" || graphContainerSelector.indexOf("..") === -1) {
                    // Absolute selector path
                    graphContainer = $(graphContainerSelector);
                } else {
                    var referenceNode = table;
                    var currentGraphContainerSelector = graphContainerSelector;

                    while (currentGraphContainerSelector.indexOf("..") !== -1) {
                        currentGraphContainerSelector = currentGraphContainerSelector.replace(/^.. /, "");
                        referenceNode = referenceNode.parent();
                    }

                    graphContainer = $(currentGraphContainerSelector, referenceNode);
                }
                if (graphContainer.length !== 1) {
                    throw "graph-container is not available in this DOM or available multiple times";
                }
                graphContainer = graphContainer[0];
            } else {
                $table.before("<div></div>");
                graphContainer = $table.prev();
                graphContainer = graphContainer[0];
            }

            // Retrieve graph type from graph-type attribute
            var globalGraphType = $table.data("graph-type");
            if (!globalGraphType) {
                throw "graph-type data attribute is mandatory";
            }
            if ($.inArray(globalGraphType, allowedGraphTypes) == -1) {
                throw "graph-container data attribute must be one of " + allowedGraphTypes.join(", ");
            }

            var stackingType = $table.data("graph-stacking");
            if (!stackingType) {
                stackingType = "normal";
            }

            var dataLabelsEnabled = $table.data("graph-datalabels-enabled");
            var isGraphInverted = $table.data("graph-inverted") == 1;

            // Retrieve series titles
            var ths = $("thead th", table);
            var columns = [];
            var vlines = [];
            var skippedColumns = 0;
            var graphIsStacked = false;
            ths.each(function(indexTh, th) {
                var $th = $(th);
                var columnScale = $th.data("graph-value-scale");

                var serieGraphType = $th.data("graph-type");
                if ($.inArray(serieGraphType, allowedGraphTypes) == -1) {
                    serieGraphType = globalGraphType;
                }

                var serieStackGroup = $th.data("graph-stack-group");
                if (serieStackGroup) {
                    graphIsStacked = true;
                }

                var serieDataLabelsEnabled = $th.data("graph-datalabels-enabled");
                if (typeof serieDataLabelsEnabled == "undefined") {
                    serieDataLabelsEnabled = dataLabelsEnabled;
                }

                var yaxis = $th.data("graph-yaxis");

                if (typeof yaxis != "undefined" && yaxis == "1") {
                    nbYaxis = 2;
                }

                var isColumnSkipped = $th.data("graph-skip") == 1;
                if (isColumnSkipped) {
                    skippedColumns = skippedColumns + 1;
                }

                var thGraphConfig = {
                    libelle: $th.text(),
                    skip: isColumnSkipped,
                    indexTd: indexTh - skippedColumns - 1,
                    color: $th.data("graph-color"),
                    visible: !$th.data("graph-hidden"),
                    yAxis: typeof yaxis != "undefined" ? yaxis : 0,
                    dashStyle: $th.data("graph-dash-style") || "solid",
                    dataLabelsEnabled: serieDataLabelsEnabled == 1,
                    dataLabelsColor: $th.data("graph-datalabels-color") || $table.data("graph-datalabels-color")

                };

                var vlinex = $th.data("graph-vline-x");
                if (typeof vlinex == "undefined") {
                    thGraphConfig.scale = typeof columnScale != "undefined" ? parseFloat(columnScale) : 1;
                    thGraphConfig.graphType = serieGraphType == "column" && isGraphInverted ? "bar" : serieGraphType;
                    thGraphConfig.stack = serieStackGroup;
                    thGraphConfig.unit = $th.data("graph-unit");
                    columns[indexTh] = thGraphConfig;
                } else {
                    thGraphConfig.x = vlinex;
                    thGraphConfig.height = $th.data("graph-vline-height");
                    thGraphConfig.name = $th.data("graph-vline-name");
                    vlines[indexTh] = thGraphConfig;
                }
            });

            var series = [];
            $(columns).each(function(indexColumn, column) {
                if (indexColumn != 0 && !column.skip) {

                    var serieConfig = {
                        name: column.libelle + (column.unit ? " (" + column.unit + ")" : ""),
                        data: [],
                        type: column.graphType,
                        stack: column.stack,
                        color: column.color,
                        visible: column.visible,
                        yAxis: column.yAxis,
                        dashStyle: column.dashStyle,
                        marker: {
                            enabled: false
                        },
                        dataLabels: {
                            enabled: column.dataLabelsEnabled,
                            color: column.dataLabelsColor,
                            align: $table.data("graph-datalabels-align") ||
                                (globalGraphType == "column" && isGraphInverted == 1 ? undefined : "center")
                        }
                    };

                    if (column.dataLabelsEnabled) {
                        var callableSerieDataLabelsFormatter = getCallable(table, "graph-datalabels-formatter");
                        if (callableSerieDataLabelsFormatter) {
                            serieConfig.dataLabels.formatter = function() {
                                return callableSerieDataLabelsFormatter(this.y);
                            };
                        }
                    }
                    series.push(serieConfig);
                }
            });

            $(vlines).each(function(indexColumn, vline) {
                if (typeof vline != "undefined" && !vline.skip) {
                    series.push({
                        name: vline.libelle,
                        data: [
                            { x: vline.x, y: 0, name: vline.name }, { x: vline.x, y: vline.height, name: vline.name }
                        ],
                        type: "spline",
                        color: vline.color,
                        visible: vline.visible,
                        marker: {
                            enabled: false
                        }
                    });
                }
            });

            var xValues = [];
            var callablePoint = getCallable(table, "graph-point-callback");
            var isGraphDatetime = $table.data("graph-xaxis-type") == "datetime";

            var rows = $("tbody:first tr", table);
            rows.each(function(indexRow, row) {
                if (!!$(row).data("graph-skip")) {
                    return;
                }

                var tds = $("td", row);
                tds.each(function(indexTd, td) {
                    var cellValue;
                    var column = columns[indexTd];

                    if (column.skip) {
                        return;
                    }
                    var $td = $(td);
                    if (indexTd == 0) {
                        cellValue = $td.text();
                        xValues.push(cellValue);
                    } else {
                        var rawCellValue = $td.text();
                        var serie = series[column.indexTd];

                        if (rawCellValue.length == 0) {
                            if (!isGraphDatetime) {
                                serie.data.push(null);
                            }
                        } else {
                            var cleanedCellValue = rawCellValue.replace(/\s/g, "").replace(/,/, ".");
                            var eventOptions = {
                                value: cleanedCellValue,
                                rawValue: rawCellValue,
                                td: $td,
                                tr: $(row),
                                indexTd: indexTd,
                                indexTr: indexRow
                            };
                            $table.trigger("highchartTable.cleanValue", eventOptions);
                            cellValue = Math.round(parseFloat(eventOptions.value) * column.scale * 100) / 100;

                            var dataGraphX = $td.data("graph-x");

                            if (isGraphDatetime) {
                                dataGraphX = $("td", $(row)).first().text();
                                var date = parseDate(dataGraphX);
                                dataGraphX = date.getTime() - date.getTimezoneOffset() * 60 * 1000;
                            }

                            var tdGraphName = $td.data("graph-name");
                            var serieDataItem = {
                                name: typeof tdGraphName != "undefined" ? tdGraphName : rawCellValue,
                                y: cellValue,
                                x: dataGraphX //undefined if no x defined in table
                            };

                            if (callablePoint) {
                                serieDataItem.events = {
                                    click: function() {
                                        return callablePoint(this);
                                    }
                                };
                            }

                            if (column.graphType === "pie") {
                                if ($td.data("graph-item-highlight")) {
                                    serieDataItem.sliced = 1;
                                }
                            }

                            var tdGraphItemColor = $td.data("graph-item-color");
                            if (typeof tdGraphItemColor != "undefined") {
                                serieDataItem.color = tdGraphItemColor;
                            }

                            serie.data.push(serieDataItem);
                        }
                    }
                });

            });

            var getYaxisAttr = function($table, yAxisNum, name) {
                var oldConvention = $table.data("graph-yaxis-" + yAxisNum + "-" + name);
                if (typeof oldConvention != "undefined") {
                    return oldConvention;
                }

                return $table.data("graph-yaxis" + yAxisNum + "-" + name);
            };

            var yAxisConfig = [];
            var yAxisNum;
            for (yAxisNum = 1; yAxisNum <= nbYaxis; yAxisNum++) {
                var yAxisConfigCurrentAxis = {
                    title: {
                        text: typeof getYaxisAttr($table, yAxisNum, "title-text") != "undefined"
                            ? getYaxisAttr($table, yAxisNum, "title-text")
                            : null
                    },
                    max: typeof getYaxisAttr($table, yAxisNum, "max") != "undefined"
                        ? getYaxisAttr($table, yAxisNum, "max")
                        : null,
                    min: typeof getYaxisAttr($table, yAxisNum, "min") != "undefined"
                        ? getYaxisAttr($table, yAxisNum, "min")
                        : null,
                    reversed: getYaxisAttr($table, yAxisNum, "reversed") == "1",
                    opposite: getYaxisAttr($table, yAxisNum, "opposite") == "1",
                    tickInterval: getYaxisAttr($table, yAxisNum, "tick-interval") || null,
                    labels: {
                        rotation: getYaxisAttr($table, yAxisNum, "rotation") || 0
                    },
                    startOnTick: getYaxisAttr($table, yAxisNum, "start-on-tick") != "0",
                    endOnTick: getYaxisAttr($table, yAxisNum, "end-on-tick") != "0",
                    stackLabels: {
                        enabled: getYaxisAttr($table, yAxisNum, "stacklabels-enabled") == "1"
                    },
                    gridLineInterpolation: getYaxisAttr($table, yAxisNum, "grid-line-interpolation") || null
                };

                var callableYAxisFormatter = getCallable(table, "graph-yaxis-" + yAxisNum + "-formatter-callback");

                if (!callableYAxisFormatter) {
                    callableYAxisFormatter = getCallable(table, "graph-yaxis" + yAxisNum + "-formatter-callback");
                }

                if (callableYAxisFormatter) {
                    yAxisConfigCurrentAxis.labels.formatter = function() {
                        return callableYAxisFormatter(this.value);
                    };
                }

                yAxisConfig.push(yAxisConfigCurrentAxis);
            }

            var defaultColors = [
                "#4572A7",
                "#AA4643",
                "#89A54E",
                "#80699B",
                "#3D96AE",
                "#DB843D",
                "#92A8CD",
                "#A47D7C",
                "#B5CA92"
            ];
            var colors = [];

            var themeColors = typeof Highcharts.theme != "undefined" && typeof Highcharts.theme.colors != "undefined"
                ? Highcharts.theme.colors
                : [];
            var lineShadow = $table.data("graph-line-shadow");
            var lineWidth = $table.data("graph-line-width") || 2;

            var nbOfColors = Math.max(defaultColors.length, themeColors.length);
            for (var i = 0; i < nbOfColors; i++) {
                var dataname = "graph-color-" + (i + 1);
                colors.push(typeof $table.data(dataname) != "undefined"
                    ? $table.data(dataname)
                    : typeof themeColors[i] != "undefined"
                    ? themeColors[i]
                    : defaultColors[i]);
            }

            var marginTop = $table.data("graph-margin-top");
            var marginRight = $table.data("graph-margin-right");
            var marginBottom = $table.data("graph-margin-bottom");
            var marginLeft = $table.data("graph-margin-left");

            var xAxisLabelsEnabled = $table.data("graph-xaxis-labels-enabled");

            var xAxisLabelStyle = {};
            var xAxisLabelFontSize = $table.data("graph-xaxis-labels-font-size");

            if (typeof xAxisLabelFontSize != "undefined") {
                xAxisLabelStyle.fontSize = xAxisLabelFontSize;
            }

            var highChartConfig = {
                colors: colors,
                chart: {
                    renderTo: graphContainer,
                    inverted: isGraphInverted,
                    marginTop: typeof marginTop != "undefined" ? marginTop : null,
                    marginRight: typeof marginRight != "undefined" ? marginRight : null,
                    marginBottom: typeof marginBottom != "undefined" ? marginBottom : null,
                    marginLeft: typeof marginLeft != "undefined" ? marginLeft : null,
                    spacingTop: $table.data("graph-spacing-top") || 10,
                    height: $table.data("graph-height") || null,
                    zoomType: $table.data("graph-zoom-type") || null,
                    polar: $table.data("graph-polar") || null
                },
                title: {
                    text: graphTitle
                },
                subtitle: {
                    text: $table.data("graph-subtitle-text") || ""
                },
                legend: {
                    enabled: $table.data("graph-legend-disabled") != "1",
                    layout: $table.data("graph-legend-layout") || "horizontal",
                    symbolWidth: $table.data("graph-legend-width") || 30,
                    x: $table.data("graph-legend-x") || 15,
                    y: $table.data("graph-legend-y") || 0
                },
                xAxis: {
                    categories: ($table.data("graph-xaxis-type") != "datetime") ? xValues : undefined,
                    type: ($table.data("graph-xaxis-type") == "datetime") ? "datetime" : undefined,
                    reversed: $table.data("graph-xaxis-reversed") == "1",
                    opposite: $table.data("graph-xaxis-opposite") == "1",
                    showLastLabel: typeof $table.data("graph-xaxis-show-last-label") != "undefined"
                        ? $table.data("graph-xaxis-show-last-label")
                        : true,
                    tickInterval: $table.data("graph-xaxis-tick-interval") || null,
                    dateTimeLabelFormats: { //by default, we display the day and month on the datetime graphs
                        second: "%e. %b",
                        minute: "%e. %b",
                        hour: "%e. %b",
                        day: "%e. %b",
                        week: "%e. %b",
                        month: "%e. %b",
                        year: "%e. %b"
                    },
                    labels:
                    {
                        rotation: $table.data("graph-xaxis-rotation") || undefined,
                        align: $table.data("graph-xaxis-align") || undefined,
                        enabled: typeof xAxisLabelsEnabled != "undefined" ? xAxisLabelsEnabled : true,
                        style: xAxisLabelStyle
                    },
                    startOnTick: $table.data("graph-xaxis-start-on-tick"),
                    endOnTick: $table.data("graph-xaxis-end-on-tick"),
                    min: getXAxisMinMax(table, "min"),
                    max: getXAxisMinMax(table, "max"),
                    alternateGridColor: $table.data("graph-xaxis-alternateGridColor") || null,
                    title: {
                        text: $table.data("graph-xaxis-title-text") || null
                    },
                    gridLineWidth: $table.data("graph-xaxis-gridLine-width") || 0,
                    gridLineDashStyle: $table.data("graph-xaxis-gridLine-style") || "ShortDot",
                    tickmarkPlacement: $table.data("graph-xaxis-tickmark-placement") || "between",
                    lineWidth: $table.data("graph-xaxis-line-width") || 0
                },
                yAxis: yAxisConfig,
                tooltip: {
                    formatter: function() {
                        if ($table.data("graph-xaxis-type") == "datetime") {
                            return "<b>" +
                                this.series.name +
                                "</b><br/>" +
                                Highcharts.dateFormat("%e. %b", this.x) +
                                " : " +
                                this.y;
                        } else {
                            var xValue = typeof xValues[this.point.x] != "undefined"
                                ? xValues[this.point.x]
                                : this.point.x;
                            if (globalGraphType === "pie") {
                                return "<strong>" +
                                    this.series.name +
                                    "</strong><br />" +
                                    xValue +
                                    " : " +
                                    this.point.y;
                            }
                            return "<strong>" + this.series.name + "</strong><br />" + xValue + " : " + this.point.name;
                        }
                    }
                },
                credits: {
                    enabled: false
                },
                plotOptions: {
                    line: {
                        dataLabels: {
                            enabled: true
                        },
                        lineWidth: lineWidth
                    },
                    area: {
                        lineWidth: lineWidth,
                        shadow: typeof lineShadow != "undefined" ? lineShadow : true,
                        fillOpacity: $table.data("graph-area-fillOpacity") || 0.75
                    },
                    pie: {
                        allowPointSelect: true,
                        dataLabels: {
                            enabled: true
                        },
                        showInLegend: $table.data("graph-pie-show-in-legend") == "1",
                        size: "80%"
                    },
                    series: {
                        animation: false,
                        stickyTracking: false,
                        stacking: graphIsStacked ? stackingType : null,
                        groupPadding: $table.data("graph-group-padding") || 0
                    }
                },
                series: series,
                exporting: {
                    filename: graphTitle.replace(/ /g, "_"),
                    buttons: {
                        exportButton: {
                            menuItems: null,
                            onclick: function() {
                                this.exportChart();
                            }
                        }
                    }
                }
            };

            $table.trigger("highchartTable.beforeRender", highChartConfig);
            new Highcharts.Chart(highChartConfig);

        });
        //for fluent api
        return this;
    };

    var getXAxisMinMax = function(table, minOrMax) {
        var value = $(table).data("graph-xaxis-" + minOrMax);
        if (typeof value != "undefined") {
            if ($(table).data("graph-xaxis-type") == "datetime") {
                var date = parseDate(value);
                return date.getTime() - date.getTimezoneOffset() * 60 * 1000;
            }
            return value;
        }
        return null;
    };

    var parseDate = function(datetime) {
        var calculatedateInfos = datetime.split(" ");
        var dateDayInfos = calculatedateInfos[0].split("-");
        var min = null;
        var hour = null;
        // If hour and minute are available in the datetime string
        if (calculatedateInfos[1]) {
            var dateHourInfos = calculatedateInfos[1].split(":");
            min = parseInt(dateHourInfos[0], 10);
            hour = parseInt(dateHourInfos[1], 10);
        }
        return new Date(parseInt(dateDayInfos[0], 10),
            parseInt(dateDayInfos[1], 10) - 1,
            parseInt(dateDayInfos[2], 10),
            min,
            hour);
    };

})(jQuery);
